<?php 
/**
 * wechat
 */
class wechat {

	//消息类型
	const MSGTYPE_TEXT 						= 'text';//文本
	const MSGTYPE_IMAGE 					= 'image';//图片
	const MSGTYPE_LOCATION 					= 'location';//地理位置
	const MSGTYPE_LINK 						= 'link';//链接
	const MSGTYPE_EVENT 					= 'event';//事件
	const MSGTYPE_MUSIC 					= 'music';//音乐
	const MSGTYPE_NEWS 						= 'news';//图文
	const MSGTYPE_VOICE 					= 'voice';//语音
	const MSGTYPE_VIDEO 					= 'video';//视频
	const MSGTYPE_SHORT_VIDEO 				= 'shortvideo';//小视频

	//事件类型，使用实例调用getRevEvent()方法取得
	const EVENT_SUBSCRIBE 					= 'subscribe';//关注
	const EVENT_UNSUBSCRIBE 				= 'unsubscribe';//取消关注
	const EVENT_SCAN 						= 'SACN';//扫描带参数二维码
	const EVENT_LOCATION 					= 'LOCATION';//上报地理位置
	const EVENT_MENU_VIEW  					= 'VIEW';//菜单 - 点击菜单跳转链接
	const EVENT_MENU_CLICK 					= 'CLICK';//菜单 - 点击菜单拉取消息
	const EVENT_MENU_SCAN_PUSH 				= 'scancode_push';//菜单 - 扫码推事件(客户端跳URL)
	const EVENT_MENU_SACN_WAITMSG 			= 'scancode_waitmsg';//菜单 - 扫码推事件(客户端不跳URL)
	const EVENT_MENU_PIC_SYS 				= 'pic_sysphoto';//菜单 - 弹出系统拍照发图
	const EVENT_MENU_PIC_PHOTO 				= 'pic_photo_or_album';//菜单 - 弹出拍照或者相册发图
	const EVENT_MENU_PIC_WEIXIN 			= 'pic_weixin';//菜单 - 弹出微信相册发图器
	const EVENT_MENU_LOCATION 				= 'location_select';//菜单 - 弹出地理位置选择器
	const EVENT_SEND_MASS 					= 'MASSSENDJOBFINISH';//发送结果 - 高级群发完成
	const EVENT_SEND_TEMPLATE 				= 'TEMPLATESENDJOBFINISH';//发送结果 - 模板消息发送结果
	const EVENT_KF_SESSION_CREATE 			= 'kfcreatesession';//多客服 - 接入会话
	const EVENT_KF_SESSION_SWITCH 			= 'kfclosesession';//多客服 - 关闭会话
	const EVENT_KF_SEESION_SWITCH 			= 'kfswitchsession';  //多客服 - 转接会话
	const EVENT_CARD_PASS 					= 'card_pass_check';//卡券 - 审核通过
	const EVENT_CARD_NOTPASS 				= 'card_not_pass_check';//卡券 - 审核未通过
	const EVENT_CARD_USER_GET 				= 'user_get_card';//卡券 - 用户领取卡券
	const EVENT_CARD_USER_DEL 				= 'user_del_card';//卡券 - 用户删除卡券

	const QR_SCENE 							= 0;//临时二维码
	const QR_LIMIT_SCENE 					= 1;//永久二维码
	

	//接口前缀 URL 全部
	const API_PREFIX 						= 'https://api.weixin.qq.com';//
	const API_CGI_PREFIX 					= 'https://api.weixin.qq.com/cgi-bin';//
	const FILE_PREFIX 						= 'http://file.api.weixin.qq.com/cgi-bin';//
	const OAUTH_PREFIX 						= 'https://open.weixin.qq.com/connect/oauth2';//
	const CGI_PREFIX	 					= 'https://mp.weixin.qq.com/cgi-bin';//

	//接口凭据 相关接口 URL 全部
	const AUTH_URL 							= '/token?grant_type=client_credential&';//获取access_token接口
	const CALLBACKSERVER_GET_URL 			= '/getcallbackip?';//获取到微信回调服务器的IP接口
	
	//发送消息 相关接口 URL
	const CS_KF_ACCOUNT_ADD_URL 			= '/customservice/kfaccount/add?';//添加客服账号
	const CS_KF_ACCOUNT_UPDATE_URL 			= '/customservice/kfaccount/update?';//设置客服信息
	const CS_KF_ACCOUNT_DEL_URL 			= '/customservice/kfaccount/del?';//删除客服账号
	const CS_KF_ACCOUNT_UPLOAD_HEADIMG_URL 	= '/customservice/kfaccount/uploadheadimg?';//上传客服头像
	const CUSTOM_SERVICE_GET_KFLIST 		= '/customservice/getkflist?';//获取客服基本信息
	const CUSTOM_SEND_URL 					= '/message/custom/send?';//发送客服消息接口
	const MEDIA_UPLOADNEWS_URL	 			= '/media/uploadnews?';//上传图文消息素材接口
	const MASS_SEND_GROUP_URL 				= '/message/mass/sendall?';//根据分组进行群发
	const MASS_SEND_URL 					= '/message/mass/send?';//根据OpenID列表群发接口
	const MEDIA_VIDEO_UPLOAD 				= '/media/uploadvideo?';//根据分组进行群发视频接口
	const MASS_DELETE_URL 					= '/message/mass/delete?';//删除群发消息
	const MASS_PREVIEW_URL 					= '/message/mass/preview?';//消息预览接口
	const MASS_QUERY_URL 					= '/message/mass/get?';//查询群发消息发送状态
	const TEMPLATE_SET_INDUSTRY_URL 		= '/template/api_set_industry?';//设置所属行业接口
	const TEMPLATE_ADD_TPL_URL 				= '/template/api_add_template?';//获得模板ID接口
	const TEMPLATE_SEND_URL 				= '/message/template/send?';//发送模板消息接口
	const MESSAGE_AUTO_REPLY				= '/get_current_autoreply_info?';//获取自动回复规则
	
	//素材 相关接口 URL
	const MEDIA_UPLOAD_URL 					= '/media/upload?';//新增临时素材
	const MEDIA_GET_URL 					= '/media/get?';//获取临时素材
	const MEDIA_FOREVER_NEWS_UPLOAD_URL 	= '/material/add_news?';//新增永久图文素材
	const MEDIA_FOREVER_UPLOAD_URL 			= '/material/add_material?';//新增其他类型永久素材
	const MEDIA_FOREVER_GET_URL 			= '/material/get_material?';//获取永久素材
	const MEDIA_FOREVER_DEL_URL 			= '/material/del_material?';//删除永久素材
	const MEDIA_FOREVER_NEWS_UPDATE_URL 	= '/material/update_news?';//修改永久图文素材
	const MEDIA_FOREVER_COUNT_URL 			= '/material/get_materialcount?';//获取素材总数
	const MEDIA_FOREVER_BATCHGET_URL 		= '/material/batchget_material?';//获取素材列表

	//用户管理 相关接口 URL
	const GROUP_CREATE_URL 					= '/groups/create?';//创建分组接口
	const GROUP_GET_URL 					= '/groups/get?';//查询所有分组接口
	const USER_GROUP_URL 					= '/groups/getid?';//查询用户所在分组接口
	const GROUP_UPDATE_URL 					= '/groups/update?';//修改分组名接口
	const GROUP_MEMBER_UPDATE_URL 			= '/groups/members/update?';//移动用户分组接口
	const GROUP_MEMBER_BATCHUPDATE_URL 		= '/groups/members/batchupdate?';//批量移动用户分组接口
	const GROUP_DELETE_URL 					= '/groups/delete?';//移动用户分组接口
	const USER_UPDATEREMARK_URL 			= '/user/info/updateremark?';//设置用户备注名接口
	const USER_INFO_URL 					= '/user/info?';//获取用户基本信息接口
	const USER_GET_URL 						= '/user/get?';//获取用户列表接口
	const OAUTH_AUTHORIZE_URL 				= '/authorize?';//用户同意授权，获取code
	const OAUTH_TOKEN_URL 					= '/access_token?';//通过code换取网页授权access_token
	const OAUTH_PEFRESH_URL 				= '/refresh_token?';//刷新access_token
	const OAUTH_USERINFO_URL 				= '/userinfo?';//拉取用户信息
	const OAUTH_AUTH_URL 					= '/auth?';//检验授权凭证（access_token）是否有效

	//自定义菜单 相关接口 URL
	const MENU_CREATE_URL 					= '/menu/create?';//创建菜单接口
	const MENU_GET_URL 						= '/menu/get?';//获取菜单接口
	const MENU_DELETE_URL 					= '/menu/delete?';//删除菜单接口
	const MENU_SELFINFO_URL 				= '/get_current_selfmenu_info?';//删除菜单接口

	//账号 相关接口 URL
	const QRCODE_CREATE_URL 				= '/qrcode/create?';//创建二维码接口
	const QRCODE_IMG_URL 					= '/showqrcode?';//通过ticket换取二维码
	const SHORT_URL 						= '/shorturl?';//二维码长链接转成短链接接口

	//数据统计 相关接口 URL 
	const DATACUBE_USER_SUMMARY 			= '/datacube/getusersummary?';
	const DATACUBE_USER_CUMULATE 			= '/datacube/getusercumulate?';
	const DATACUBE_ARTICLE_SUMMARY 			= '/datacube/getarticlesummary?';
	const DATACUBE_ARTICLE_TOTAL 			= '/datacube/getarticletotal?';
	const DATACUBE_ARTICLE_READ 			= '/datacube/getuserread?';
	const DATACUBE_ARTICLE_READHOUR 		= '/datacube/getuserreadhour?';
	const DATACUBE_ARTICLE_SHARE 			= '/datacube/getusershare?';
	const DATACUBE_ARTICLE_SHAREHOUR 		= '/datacube/getusersharehour?';
	const DATACUBE_UPSTREAMMSG_SUMMARY  	= '/datacube/getupstreammsg?';
	const DATACUBE_UPSTREAMMSG_HOUR 		= '/datacube/getupstreammsghour?';
	const DATACUBE_UPSTREAMMSG_WEEK 		= '/datacube/getupstreammsgweek?';
	const DATACUBE_UPSTREAMMSG_MONTH   		= '/datacube/getupstreammsgmonth?';
	const DATACUBE_UPSTREAMMSG_DIST 		= '/datacube/getupstreammsgdist?';
	const DATACUBE_UPSTREAMMSG_DISTWEEK  	= '/datacube/getupstreammsgdistweek?';
	const DATACUBE_UPSTREAMMSG_DISTMONTH 	= '/datacube/getupstreammsgdistmonth?';
	const DATACUBE_INTERFACE_SUMMARY 		= '/datacube/getinterfacesummary?';
	const DATACUBE_INTERFACE_SUMMARYHOUR 	= '/datacube/getinterfacesummaryhour?';

	//小店 相关接口 URL

	//卡券 相关接口 URL
	const CARD_UPLOADIMG 					= '/card/uploadimg?';//上传卡券LOGO
	const CARD_GETCOLORS 					= '/card/getcolors?';//设置卡券背景颜色
	const CARD_CREATE 						= '/card/create?';//创建卡券
	const CARD_MEMBERCARD_ACTIVATE 			= '/card/membercard/activate?';//激活/绑定会员卡
	const CARD_MEMBERCARD_UPDATEUSER 		= '/card/membercard/updateuser?';//更新会员信息
	const CARD_QRCODE_CREATE 				= '/card/qrcode/create?';//创建二维码接口
	const CARD_CODE_GET 					= '/card/code/get?';//查询code
	const CARD_TESTWHILELIST_SET 			= '/card/testwhitelist/set?';//设置卡券测试白名单
	const CARD_CODE_CONSUME 				= '/card/code/consume?';//核销卡券Code接口
	const CARD_CODE_DECRYPT 				= '/card/code/decrypt?';//Code解码接口
	const CARD_USER_LIST	 				= '/card/user/getcardlist?';//获取用户已领取卡券接口
	const CARD_GET 							= '/card/get?';//查看卡券详情
	const CARD_BATCHGET 					= '/card/batchget?';//批量查询卡列表
	const CARD_UPDATE 						= '/card/update?';//更改卡券信息接口
	const CARD_MODIFY_STOCK 				= '/card/modifystock?';//修改库存接口
	const CARD_CODE_UPDATE 					= '/card/code/update?';//更改Code接口
	const CARD_DELETE 						= '/card/delete?';//删除卡券接口
	const CARD_CODE_UNAVAILABLE 			= '/card/code/unavailable?';//设置卡券失效接口
	const CARD_MEETINGTICKET_UPDATEUSER 	= '/card/meetingticket/updateuser?';//更新会议门票
	const CARD_MOVIETICKET_UPDATEUSER 		= '/card/movieticket/updateuser?';//更新电影票
	const CARD_BOARDINGPASS_CHECKIN 		= '/card/boardingpass/checkin?';//更新飞机票信息接口

	//门店 相关接口 URL
	const MERCHANT_CREATE_URL 				= '/merchant/create?';//增加商品
	const MERCHANT_DELETE_URL 				= '/merchant/del?';//删除商品
	const MERCHANT_UPDATE_URL 				= '/merchant/update?';//修改商品
	const MERCHANT_GET_URL 					= '/merchant/get?';//查询商品
	const MERCHANT_GET_STATUS_URL 			= '/merchant/getbystatus?';//获取指定状态的所有商品
	const MERCHANT_MOD_PRODUCT_STATUS_URL 	= '/merchant/modproductstatus?';//商品上下架
	const MERCHANT_CATEGORY_GET_SUB_URL 	= '/merchant/category/getsub?';//获取指定分类的所有子分类
	const MERCHANT_CATEGORY_GET_SKU_URL 	= '/merchant/category/getsku?';//获取指定子分类的所有SKU
	const MERCHANT_CATEGORY_PROPERTY_URL	= '/merchant/category/getproperty?';//获取指定分类的所有属性
	const MERCHANT_STOCK_ADD_URL 			= '/merchant/stock/add?';//增加库存
	const MERCHANT_STOCK_REDUCE_URL 		= '/merchant/stock/reduce?';//减少库存
	const MERCHANT_EXPRESS_ADD_URL 			= '/merchant/express/add?';//增加邮费模板
	const MERCHANT_EXPRESS_DEL_URL 			= '/merchant/express/del?';//删除邮费模板
	const MERCHANT_EXPRESS_UPDATE_URL 		= '/merchant/express/update?';//修改邮费模板
	const MERCHANT_EXPRESS_GET_ID_URL 		= '/merchant/express/getbyid?';//获取指定ID的邮费模版
	const MERCHANT_EXPRESS_GET_ALL_URL 		= '/merchant/express/getall?';//获取所有邮费模板
	const MERCHANT_GROUP_ADD_URL 			= '/merchant/group/add?';//增加分组
	const MERCHANT_GROUP_DEL_URL 			= '/merchant/group/del?';//删除分组
	const MERCHANT_GROUP_PROPERTY_MOD_URL 	= '/merchant/group/propertymod?';//修改分组属性
	const MERCHANT_GROUP_PRODUCT_MOD_URL 	= '/merchant/group/productmod?';//修改分组商品
	const MERCHANT_GROUP_GET_ALL_URL 		= '/merchant/group/getall?';//获取所有分组
	const MERCHANT_GROUP_GET_BY_ID_URL 		= '/merchant/group/getbyid?';//根据分组ID获取分组信息
	const MERCHANT_SHELF_ADD_URL 			= '/merchant/shelf/add?';//增加货架
	const MERCHANT_SHELF_DEL_URL 			= '/merchant/shelf/del?';//删除货架
	const MERCHANT_SHELF_MOD_URL 			= '/merchant/shelf/mod?';//修改货架
	const MERCHANT_SHELF_GET_ALL_URL 		= '/merchant/shelf/getall?';//获取所有货架
	const MERCHANT_SHELF_GET_ID_URL 		= '/merchant/shelf/getbyid?';//根据货架ID获取货架信息
	const MERCHANT_ORDER_GET_BY_ID_URL 		= '/merchant/order/getbyid?';//根据订单ID获取订单详情
	const MERCHANT_ORDER_GET_BY_FILTER_URL 	= '/merchant/order/getbyfilter?';//根据订单状态/创建时间获取订单详情
	const MERCHANT_ORDER_SET_DELIVERY_URL 	= '/merchant/order/setdelivery?';//设置订单发货信息
	const MERCHANT_ORDER_CLOSE_URL 			= '/merchant/order/close?';//关闭订单
	const MERCHANT_COMMON_UPLOAD_IMG_URL 	= '/merchant/common/upload_img?';//上传图片
	
	//智能 相关接口 URL
	const SEMANTIC_API_URL 					= '/semantic/semproxy/search?';

	//多客服 相关接口 URL
	const CUSTOM_SERVICE_GET_ONLINEKFLIST 	= '/customservice/getonlinekflist?';//获取在线客服接待信息
	const CUSTOM_SESSION_CREATE 			= '/customservice/kfsession/create?';//创建会话
	const CUSTOM_SESSION_CLOSE 				= '/customservice/kfsession/close?';//关闭会话
	const CUSTOM_SESSION_GET 				= '/customservice/kfsession/getsession?';//获取客户的会话状态
	const CUSTOM_SESSION_GET_LIST 			= '/customservice/kfsession/getsessionlist?';//获取客服的会话列表
	const CUSTOM_SESSION_GET_WAIT 			= '/customservice/kfsession/getwaitcase?';//获取未接入会话列表
	const CUSTOM_SERVICE_GET_RECORD 		= '/customservice/msgrecord/getrecord?';//获取客服聊天记录接口

	//摇一摇周边 相关接口 URL
	const SHAKEAROUND_DEVICE_APPLYID 		= '/shakearound/device/applyid?';//申请设备ID
	const SHAKEAROUND_DEVICE_UPDATE 		= '/shakearound/device/update?';//编辑设备信息
	const SHAKEAROUND_DEVICE_BINDLOCATION 	= '/shakearound/device/bindlocation?';//配置设备与门店的关联关系
	const SHAKEAROUND_DEVICE_SEARCH 		= '/shakearound/device/search?';//查询设备列表
	const SHAKEAROUND_PAGE_ADD 				= '/shakearound/page/add?';//新增页面
	const SHAKEAROUND_PAGE_UPDATE 			= '/shakearound/page/update?';//编辑页面信息
	const SHAKEAROUND_PAGE_SEARCH 			= '/shakearound/page/search?';//查询页面列表
	const SHAKEAROUND_PAGE_DELETE 			= '/shakearound/page/delete?';//删除页面
	const SHAKEAROUND_MATERIAL_ADD 			= '/shakearound/material/add?';//上传图片素材
	const SHAKEAROUND_DEVICE_BINDPAGE 		= '/shakearound/device/bindpage?';//配置设备与页面的关联关系
	const SHAKEAROUND_USER_GETSHAKEINFO 	= '/shakearound/user/getshakeinfo?';//获取摇周边的设备及用户信息
	const SHAKEAROUND_STATISTICS_DEVICE 	= '/shakearound/statistics/device?';//以设备为维度的数据统计接口
	const SHAKEAROUND_STATISTICS_PAGE 		= '/shakearound/statistics/page?';//以页面为维度的数据统计接口

	private $token;
	private $encodingAesKey;
	private $encrypt_type;
	private $appid;
	private $appsecret;
	private $access_token;
	private $jsapi_ticket;
	private $user_token;
	private $partnerid;
	private $partnerkey;
	private $paysignkey;
	private $postxml;
	private $_msg;
	private $_funcflag = false;
	private $_receive;
	private $_text_filter = false;
	public $debug = false;
	public $errCode = 40001;
	public $errMsg = "No Access";
	public $logcallback;


	//初始化配置
	public function __construct($config = array()){
		$this->token = isset($config['token']) ? $config['token'] : "";
		$this->encodingAesKey = isset($config['encodingAesKey']) ? $config['encodingAesKey'] : "";
		$this->appid = isset($config['appid']) ? $config['appid'] : "";
		$this->appsecret = isset($config['appsecret']) ? $config['appsecret'] : "";
		$this->debug = isset($config['debug']) ? $config['debug'] : false;
		$this->logcallback = isset($config['logcallback']) ? $config['logcallback'] : false;
	}


	/**
	 * 检查签名
	 * @param  string $str [description]
	 * @return [type]      [description]
	 */
	public function checkSignature($encryptStr = ''){
		$signature = isset($_GET["signature"]) ? $_GET["signature"] : "";
		$signature = isset($_GET["msg_signature"]) ? $_GET["msg_signature"] : $signature;//如果存在加密验证则用加密验证段
		$timestamp = isset($_GET["timestamp"]) ? $_GET["timestamp"] : "";
		$nonce = isset($_GET["nonce"]) ? $_GET["nonce"] : "";

		$tmpArr = array($this->token, $timestamp, $nonce, $encryptStr);
		sort($tmpArr, SORT_STRING);
		$tmpStr = implode($tmpArr);
		$tmpStr = sha1($tmpStr);
		if($tmpStr == $signature)
			return true;
		return false;
	}


	/**
	 * 验证
	 * @param  boolean $return [description]
	 * @return [type]          [description]
	 */
	public function valid($return = false){
		$encryptStr = "";
		if($_SERVER['REQUEST_METHOD'] == "POST"){
			$postStr = file_get_contents("php://input");
			$array = (array)simplexml_load_string($postStr, "SimpleXMLElement", LIBXML_NOCDATA);
			$this->encrypt_type = isset($_GET["encrypt_type"]) ? $_GET["encrypt_type"] : "";
			
			if($this->encrypt_type == 'aes'){
				$encryptStr = $array['Encrypt'];
				$pc = new Prpcrypt($this->encodingAesKey);
				$array = $pc->decrypt($encryptStr, $this->appid);
				if(!isset($array[0]) || $array[0] != 0){
					if(!$return)
						die("decrypt error");
					return false;
				}
				$this->postxml = $array[1];
				if(!$this->appid)
					$this->appid = $array[2];//为了没有appid的订阅号
			}else{
				$this->postxml = $postStr;
			}

		}elseif(isset($_GET["echostr"])){
			$echoStr = $_GET["echostr"];
			if($return){
				if($this->checkSignature())
					return $echoStr;
				else
					return false;
			}else{
				if($this->checkSignature())
					die($echoStr);
				else
					die("no access 1");
			}
		}

		if(!$this->checkSignature($encryptStr)){
			if($return)
				return false;
			else
				die('no access 2');
		}
		return true;
	}

	/**
	 * 获取access_token
	 * @param string $appid 如在类初始化时已提供，则可为空
	 * @param string $appsecret 如在类初始化时已提供，则可为空
	 * @param string $token 手动指定access_token，非必要情况不建议用
	 */
	public function checkAuth($appid = '', $appsecret = '', $token = ''){
		if (!$appid || !$appsecret) {
			$appid = $this->appid;
			$appsecret = $this->appsecret;
		}
		if ($token) { //手动指定token，优先使用
		    $this->access_token=$token;
		    return $this->access_token;
		}

		// $authname = 'wechat_access_token'.$appid;
		// if ($rs = $this->getCache($authname))  {
		// 	$this->access_token = $rs;
		// 	return $rs;
		// }

		$url = self::API_CGI_PREFIX.self::AUTH_URL.'appid='.$appid.'&secret='.$appsecret;
		$result = $this->http_get($url);
		if ($result){
			$json = json_decode($result,true);
			if (!$json || isset($json['errcode'])) {
				$this->errCode = $json['errcode'];
				$this->errMsg = $json['errmsg'];
				return false;
			}
			$this->access_token = $json['access_token'];
			$expire = $json['expires_in'] ? intval($json['expires_in'])-100 : 3600;
			//$this->setCache($authname,$this->access_token,$expire);
			return $this->access_token;
		}
		return false;
	}


	/**
	 * 设置发送消息
	 * @param  string  $msg    [description]
	 * @param  boolean $append [description]
	 * @return [type]          [description]
	 */
	public function message($msg = '', $append = false){
		if(is_null($msg)){
			$this->_msg = array();
		}elseif(is_array($msg)){
			if($append)
				$this->_msg = array_merge($this->_msg, $msg);
			else
				$this->_msg = $msg;
			return $this->_msg;
		}else{
			return $this->_msg;
		}
	}

	/**
	 * 获取微信服务器发来的消息
	 * @return [type] [description]
	 */
	public function getRev(){
		if($this->_receive) return $this;
		$postStr = !empty($this->postxml) ? $this->postxml : file_get_contents("php://input");
		if(!empty($postStr))
			$this->_receive = (array)simplexml_load_string($postStr, "SimpleXMLElement", LIBXML_NOCDATA);
		return $this;
	}

	public function getRevData(){
		return $this->_receive;
	}

	/**
	 * 获取消息发送者
	 * @return [type] [description]
	 */
	public function getRevFrom(){
		if(isset($this->_receive['FromUserName']))
			return $this->_receive['FromUserName'];
		else
			return false;
	}

	/**
	 * 获取消息接收者
	 * @return [type] [description]
	 */
	public function getRevTo(){
		if(isset($this->_receive['ToUserName']))
			return $this->_receive['ToUserName'];
		else
			return false;
	}

	public function getRevContent(){
		if (isset($this->_receive['Content']))
			return $this->_receive['Content'];
		else
			return false;
	}

	public function getRevRecognition(){
		if (isset($this->_receive['Recognition'])) //获取语音识别文字内容，需申请开通
			return $this->_receive['Recognition'];
		else
			return false;
	}

	/**
	 * 获取接收消息的类型
	 * @return [type] [description]
	 */
	public function getRevType(){
		if(isset($this->_receive['MsgType']))
			return $this->_receive['MsgType'];
		else
			return false;
	}


	public function getRevEvent(){
		if(isset($this->_receive['Event']))
			return $this->_receive['Event'];
		else
			return false;
	}

	public function getRevEventKey(){
		if(isset($this->_receive['EventKey']))
			return $this->_receive['EventKey'];
		else
			return false;
	}

	/**
	 * 文本信息
	 * @param  string $text [description]
	 * @return [type]       [description]
	 */
	public function text($text = ''){
		$FuncFlag = $this->_funcflag ? 1 : 0;
		$msg = array(
			'ToUserName' => $this->getRevFrom(),
			'FromUserName'=>$this->getRevTo(),
			'MsgType'=>self::MSGTYPE_TEXT,
			'Content'=>$this->_auto_text_filter($text),
			'CreateTime'=>time(),
			'FuncFlag'=>$FuncFlag
		);
		$this->message($msg);
		return $this;
	}


	public function replyEmpty(){
		echo "success";
	}

	/**
	 * 回复
	 * @param  array   $msg    [description]
	 * @param  boolean $return [description]
	 * @return [type]          [description]
	 */
	public function reply($msg = array(), $return = false){
		if (empty($msg)) {
		    if (empty($this->_msg))   //防止不先设置回复内容，直接调用reply方法导致异常
		        return false;
			$msg = $this->_msg;
		}
		$xmldata=  $this->xml_encode($msg);
		if ($this->encrypt_type == 'aes') { //如果来源消息为加密方式
		    $pc = new Prpcrypt($this->encodingAesKey);
		    $array = $pc->encrypt($xmldata, $this->appid);
		    $ret = $array[0];
		    if ($ret != 0) {
		        return false;
		    }
		    $timestamp = time();
		    $nonce = rand(77,999)*rand(605,888)*rand(11,99);
		    $encrypt = $array[1];
		    $tmpArr = array($this->token, $timestamp, $nonce,$encrypt);//比普通公众平台多了一个加密的密文
		    sort($tmpArr, SORT_STRING);
		    $signature = implode($tmpArr);
		    $signature = sha1($signature);
		    $xmldata = $this->generate($encrypt, $signature, $timestamp, $nonce);
		}
		//util::log($xmldata);
		if ($return)
			return $xmldata;
		else
			echo $xmldata;
	} 

	public function text_custom($openId = '', $text = ''){
		$msg = array(
			'touser' => $openId,
			'msgtype'=> self::MSGTYPE_TEXT,
			'text'=>array(
				"content" => $text
			)
		);

		$this->message($msg);
		return $this;
	}




	/**
	 * 发送客服消息
	 * @param array $data 消息结构{"touser":"OPENID","msgtype":"news","news":{...}}
	 * @return boolean|array
	 */
	public function send(){
		if (!$this->access_token && !$this->checkAuth()) return false;
		$result = $this->http_post(self::API_CGI_PREFIX.self::CUSTOM_SEND_URL.'access_token='.$this->access_token,self::json_encode($this->_msg));
		if ($result)
		{
			$json = json_decode($result,true);
			if (!$json || !empty($json['errcode'])) {
				$this->errCode = $json['errcode'];
				$this->errMsg = $json['errmsg'];
				util::log("111");
				return false;
				util::log("222");
			}
			util::log("333");
			return $json;
			util::log("444");
		}
		util::log("555");
		return false;
		util::log("666");
	}


	/**
	 * 创建菜单(认证后的订阅号可用)
	 * @param array $data 菜单数组数据
	 * example:
     * 	array (
     * 	    'button' => array (
     * 	      0 => array (
     * 	        'name' => '扫码',
     * 	        'sub_button' => array (
     * 	            0 => array (
     * 	              'type' => 'scancode_waitmsg',
     * 	              'name' => '扫码带提示',
     * 	              'key' => 'rselfmenu_0_0',
     * 	            ),
     * 	            1 => array (
     * 	              'type' => 'scancode_push',
     * 	              'name' => '扫码推事件',
     * 	              'key' => 'rselfmenu_0_1',
     * 	            ),
     * 	        ),
     * 	      ),
     * 	      1 => array (
     * 	        'name' => '发图',
     * 	        'sub_button' => array (
     * 	            0 => array (
     * 	              'type' => 'pic_sysphoto',
     * 	              'name' => '系统拍照发图',
     * 	              'key' => 'rselfmenu_1_0',
     * 	            ),
     * 	            1 => array (
     * 	              'type' => 'pic_photo_or_album',
     * 	              'name' => '拍照或者相册发图',
     * 	              'key' => 'rselfmenu_1_1',
     * 	            )
     * 	        ),
     * 	      ),
     * 	      2 => array (
     * 	        'type' => 'location_select',
     * 	        'name' => '发送位置',
     * 	        'key' => 'rselfmenu_2_0'
     * 	      ),
     * 	    ),
     * 	)
     * type可以选择为以下几种，其中5-8除了收到菜单事件以外，还会单独收到对应类型的信息。
     * 1、click：点击推事件
     * 2、view：跳转URL
     * 3、scancode_push：扫码推事件
     * 4、scancode_waitmsg：扫码推事件且弹出“消息接收中”提示框
     * 5、pic_sysphoto：弹出系统拍照发图
     * 6、pic_photo_or_album：弹出拍照或者相册发图
     * 7、pic_weixin：弹出微信相册发图器
     * 8、location_select：弹出地理位置选择器
	 */
	public function createMenu($data){
		if (!$this->access_token && !$this->checkAuth()) return false;
		$url = self::API_CGI_PREFIX.self::MENU_CREATE_URL.'access_token='.$this->access_token;
		$data = self::json_encode($data);
		$result = $this->http_post($url,$data);
		if ($result) {
			$json = json_decode($result,true);
			if (!$json || !empty($json['errcode'])) {
				$this->errCode = $json['errcode'];
				$this->errMsg = $json['errmsg'];
				return false;
			}
			return true;
		}
		return false;
	}




	/**
	 * 过滤文字回复\r\n换行符
	 * @param  [type] $text [description]
	 * @return [type]       [description]
	 */
	public function _auto_text_filter($text){
		if (!$this->_text_filter) return $text;
		return str_replace("\r\n", "\n", $text);
	}

	/**
	 * 数据XML编码
	 * @param  [type] $data [description]
	 * @return [type]       [description]
	 */
	public function data_to_xml($data){
		$xml = '';
	    foreach ($data as $key => $val) {
	        is_numeric($key) && $key = "item id=\"$key\"";
	        $xml    .=  "<$key>";
	        $xml    .=  ( is_array($val) || is_object($val)) ? self::data_to_xml($val)  : self::xmlSafeStr($val);
	        list($key, ) = explode(' ', $key);
	        $xml    .=  "</$key>";
	    }
	    return $xml;
	}

	public static function xmlSafeStr($str){
		return '<![CDATA['.preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/",'',$str).']]>';
	}

	/**
	 * XML编码
	 * @param  [type] $data     [description]
	 * @param  string $root     [description]
	 * @param  string $item     [description]
	 * @param  string $attr     [description]
	 * @param  string $id       [description]
	 * @param  string $encoding [description]
	 * @return [type]           [description]
	 */
	public function xml_encode($data, $root = 'xml', $item = 'item', $attr = '', $id='id', $encoding='utf-8'){
		if(is_array($attr)){
	        $_attr = array();
	        foreach ($attr as $key => $value) {
	            $_attr[] = "{$key}=\"{$value}\"";
	        }
	        $attr = implode(' ', $_attr);
	    }
	    $attr   = trim($attr);
	    $attr   = empty($attr) ? '' : " {$attr}";
	    $xml   = "<{$root}{$attr}>";
	    $xml   .= self::data_to_xml($data, $item, $id);
	    $xml   .= "</{$root}>";
	    return $xml;
	}

	/**
	 * 微信api不支持中文转义的json结构
	 * @param array $arr
	 */
	public static function json_encode($arr) {
		$parts = array ();
		$is_list = false;
		//Find out if the given array is a numerical array
		$keys = array_keys ( $arr );
		$max_length = count ( $arr ) - 1;
		if (($keys [0] === 0) && ($keys [$max_length] === $max_length )) { //See if the first key is 0 and last key is length - 1
			$is_list = true;
			for($i = 0; $i < count ( $keys ); $i ++) { //See if each key correspondes to its position
				if ($i != $keys [$i]) { //A key fails at position check.
					$is_list = false; //It is an associative array.
					break;
				}
			}
		}
		foreach ( $arr as $key => $value ) {
			if (is_array ( $value )) { //Custom handling for arrays
				if ($is_list)
					$parts [] = self::json_encode ( $value ); /* :RECURSION: */
				else
					$parts [] = '"' . $key . '":' . self::json_encode ( $value ); /* :RECURSION: */
			} else {
				$str = '';
				if (! $is_list)
					$str = '"' . $key . '":';
				//Custom handling for multiple data types
				if (!is_string ( $value ) && is_numeric ( $value ) && $value<2000000000)
					$str .= $value; //Numbers
				elseif ($value === false)
				$str .= 'false'; //The booleans
				elseif ($value === true)
				$str .= 'true';
				else
					$str .= '"' . addslashes ( $value ) . '"'; //All other things
				// :TODO: Is there any more datatype we should be in the lookout for? (Object?)
				$parts [] = $str;
			}
		}
		$json = implode ( ',', $parts );
		if ($is_list)
			return '[' . $json . ']'; //Return numerical JSON
		return '{' . $json . '}'; //Return associative JSON
	}

	/**
	 * http get
	 * @param  [type] $url [description]
	 * @return [type]      [description]
	 */
	public function http_get($url){
		$oCurl = curl_init();
		if(stripos($url,"https://")!==FALSE){
			curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
			curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
			curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
		}
		curl_setopt($oCurl, CURLOPT_URL, $url);
		curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
		$sContent = curl_exec($oCurl);
		$aStatus = curl_getinfo($oCurl);
		curl_close($oCurl);
		if(intval($aStatus["http_code"])==200){
			return $sContent;
		}else{
			return false;
		}
	}

	/**
	 * http post
	 * @param  [type]  $url       [description]
	 * @param  [type]  $param     [description]
	 * @param  boolean $post_file [description]
	 * @return [type]             [description]
	 */
	public function http_post($url, $param, $post_file = false){
		$oCurl = curl_init();
		if(stripos($url,"https://")!==FALSE){
			curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
			curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
			curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
		}
		if (is_string($param) || $post_file) {
			$strPOST = $param;
		} else {
			$aPOST = array();
			foreach($param as $key=>$val){
				$aPOST[] = $key."=".urlencode($val);
			}
			$strPOST =  join("&", $aPOST);
		}
		curl_setopt($oCurl, CURLOPT_URL, $url);
		curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
		curl_setopt($oCurl, CURLOPT_POST,true);
		curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST);
		$sContent = curl_exec($oCurl);
		$aStatus = curl_getinfo($oCurl);
		curl_close($oCurl);
		if(intval($aStatus["http_code"])==200){
			return $sContent;
		}else{
			return false;
		}
	}
}


/**
 * PKCS7Encoder class
 *
 * 提供基于PKCS7算法的加解密接口.
 */
class PKCS7Encoder{
	public static $block_size = 32;

	/**
	 * 对需要加密的明文进行填充补位
	 * @param $text 需要进行填充补位操作的明文
	 * @return 补齐明文字符串
	 */
	function encode($text){
		$block_size = PKCS7Encoder::$block_size;
		$text_length = strlen($text);
		//计算需要填充的位数
		$amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
		if ($amount_to_pad == 0) {
			$amount_to_pad = PKCS7Encoder::block_size;
		}
		//获得补位所用的字符
		$pad_chr = chr($amount_to_pad);
		$tmp = "";
		for ($index = 0; $index < $amount_to_pad; $index++) {
			$tmp .= $pad_chr;
		}
		return $text . $tmp;
	}

	/**
	 * 对解密后的明文进行补位删除
	 * @param decrypted 解密后的明文
	 * @return 删除填充补位后的明文
	 */
	function decode($text){

		$pad = ord(substr($text, -1));
		if ($pad < 1 || $pad > 32) {
			$pad = 0;
		}
		return substr($text, 0, (strlen($text) - $pad));
	}

}

/**
 * Prpcrypt class
 *
 * 提供接收和推送给公众平台消息的加解密接口.
 */
class Prpcrypt{
	public $key;

	function Prpcrypt($k){
		$this->key = base64_decode($k . "=");
	}

	/**
	 * 对明文进行加密
	 * @param string $text 需要加密的明文
	 * @return string 加密后的密文
	 */
	public function encrypt($text, $appid){

		try {
			//获得16位随机字符串，填充到明文之前
			$random = $this->getRandomStr();
			$text = $random . pack("N", strlen($text)) . $text . $appid;
			// 网络字节序
			$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
			$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
			$iv = substr($this->key, 0, 16);
			//使用自定义的填充方式对明文进行补位填充
			$pkc_encoder = new PKCS7Encoder;
			$text = $pkc_encoder->encode($text);
			mcrypt_generic_init($module, $this->key, $iv);
			//加密
			$encrypted = mcrypt_generic($module, $text);
			mcrypt_generic_deinit($module);
			mcrypt_module_close($module);

			//print(base64_encode($encrypted));
			//使用BASE64对加密后的字符串进行编码
			return array(ErrorCode::$OK, base64_encode($encrypted));
		} catch (Exception $e) {
			//print $e;
			return array(ErrorCode::$EncryptAESError, null);
		}
	}

	/**
	 * 对密文进行解密
	 * @param string $encrypted 需要解密的密文
	 * @return string 解密得到的明文
	 */
	public function decrypt($encrypted, $appid){

		try {
			//使用BASE64对需要解密的字符串进行解码
			$ciphertext_dec = base64_decode($encrypted);
			$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
			$iv = substr($this->key, 0, 16);
			mcrypt_generic_init($module, $this->key, $iv);

			//解密
			$decrypted = mdecrypt_generic($module, $ciphertext_dec);
			mcrypt_generic_deinit($module);
			mcrypt_module_close($module);
		} catch (Exception $e) {
			return array(ErrorCode::$DecryptAESError, null);
		}


		try {
			//去除补位字符
			$pkc_encoder = new PKCS7Encoder;
			$result = $pkc_encoder->decode($decrypted);
			//去除16位随机字符串,网络字节序和AppId
			if (strlen($result) < 16)
				return "";
			$content = substr($result, 16, strlen($result));
			$len_list = unpack("N", substr($content, 0, 4));
			$xml_len = $len_list[1];
			$xml_content = substr($content, 4, $xml_len);
			$from_appid = substr($content, $xml_len + 4);
		} catch (Exception $e) {
			//print $e;
			return array(ErrorCode::$IllegalBuffer, null);
		}
		if ($from_appid != $appid)
			return array(ErrorCode::$ValidateAppidError, null);
		return array(0, $xml_content);

	}


	/**
	 * 随机生成16位字符串
	 * @return string 生成的字符串
	 */
	function getRandomStr(){

		$str = "";
		$str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
		$max = strlen($str_pol) - 1;
		for ($i = 0; $i < 16; $i++) {
			$str .= $str_pol[mt_rand(0, $max)];
		}
		return $str;
	}

}

/**
 * XMLParse class
 *
 * 提供提取消息格式中的密文及生成回复消息格式的接口.
 */
class XMLParse{

	/**
	 * 提取出xml数据包中的加密消息
	 * @param string $xmltext 待提取的xml字符串
	 * @return string 提取出的加密消息字符串
	 */
	public function extract($xmltext){
		try {
			$xml = new DOMDocument();
			$xml->loadXML($xmltext);
			$array_e = $xml->getElementsByTagName('Encrypt');
			$array_a = $xml->getElementsByTagName('ToUserName');
			$encrypt = $array_e->item(0)->nodeValue;
			$tousername = $array_a->item(0)->nodeValue;
			return array(0, $encrypt, $tousername);
		} catch (Exception $e) {
			//print $e . "\n";
			return array(ErrorCode::$ParseXmlError, null, null);
		}
	}

	/**
	 * 生成xml消息
	 * @param string $encrypt 加密后的消息密文
	 * @param string $signature 安全签名
	 * @param string $timestamp 时间戳
	 * @param string $nonce 随机字符串
	 */
	public function generate($encrypt, $signature, $timestamp, $nonce){
		$format = "<xml>
			<Encrypt><![CDATA[%s]]></Encrypt>
			<MsgSignature><![CDATA[%s]]></MsgSignature>
			<TimeStamp>%s</TimeStamp>
			<Nonce><![CDATA[%s]]></Nonce>
			</xml>";
		return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
	}

}

/**
 * 1.第三方回复加密消息给公众平台；
 * 2.第三方收到公众平台发送的消息，验证消息的安全性，并对消息进行解密。
 */
class WXBizMsgCrypt{
	private $token;
	private $encodingAesKey;
	private $appId;

	/**
	 * 构造函数
	 * @param $token string 公众平台上，开发者设置的token
	 * @param $encodingAesKey string 公众平台上，开发者设置的EncodingAESKey
	 * @param $appId string 公众平台的appId
	 */
	public function WXBizMsgCrypt($token, $encodingAesKey, $appId){
		$this->token = $token;
		$this->encodingAesKey = $encodingAesKey;
		$this->appId = $appId;
	}

	/**
	 * 将公众平台回复用户的消息加密打包.
	 * <ol>
	 *    <li>对要发送的消息进行AES-CBC加密</li>
	 *    <li>生成安全签名</li>
	 *    <li>将消息密文和安全签名打包成xml格式</li>
	 * </ol>
	 *
	 * @param $replyMsg string 公众平台待回复用户的消息，xml格式的字符串
	 * @param $timeStamp string 时间戳，可以自己生成，也可以用URL参数的timestamp
	 * @param $nonce string 随机串，可以自己生成，也可以用URL参数的nonce
	 * @param &$encryptMsg string 加密后的可以直接回复用户的密文，包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
	 *                      当return返回0时有效
	 *
	 * @return int 成功0，失败返回对应的错误码
	 */
	public function encryptMsg($replyMsg, $timeStamp, $nonce, &$encryptMsg){
		$pc = new Prpcrypt($this->encodingAesKey);

		//加密
		$array = $pc->encrypt($replyMsg, $this->appId);
		$ret = $array[0];
		if ($ret != 0) {
			return $ret;
		}

		if ($timeStamp == null) {
			$timeStamp = time();
		}
		$encrypt = $array[1];

		//生成安全签名
		$sha1 = new SHA1;
		$array = $sha1->getSHA1($this->token, $timeStamp, $nonce, $encrypt);
		$ret = $array[0];
		if ($ret != 0) {
			return $ret;
		}
		$signature = $array[1];

		//生成发送的xml
		$xmlparse = new XMLParse;
		$encryptMsg = $xmlparse->generate($encrypt, $signature, $timeStamp, $nonce);
		return ErrorCode::$OK;
	}


	/**
	 * 检验消息的真实性，并且获取解密后的明文.
	 * <ol>
	 *    <li>利用收到的密文生成安全签名，进行签名验证</li>
	 *    <li>若验证通过，则提取xml中的加密消息</li>
	 *    <li>对消息进行解密</li>
	 * </ol>
	 *
	 * @param $msgSignature string 签名串，对应URL参数的msg_signature
	 * @param $timestamp string 时间戳 对应URL参数的timestamp
	 * @param $nonce string 随机串，对应URL参数的nonce
	 * @param $postData string 密文，对应POST请求的数据
	 * @param &$msg string 解密后的原文，当return返回0时有效
	 *
	 * @return int 成功0，失败返回对应的错误码
	 */
	public function decryptMsg($msgSignature, $timestamp = null, $nonce, $postData, &$msg){
		if (strlen($this->encodingAesKey) != 43) {
			return ErrorCode::$IllegalAesKey;
		}

		$pc = new Prpcrypt($this->encodingAesKey);

		//提取密文
		$xmlparse = new XMLParse;
		$array = $xmlparse->extract($postData);
		$ret = $array[0];

		if ($ret != 0) {
			return $ret;
		}

		if ($timestamp == null) {
			$timestamp = time();
		}

		$encrypt = $array[1];
		$touser_name = $array[2];

		//验证安全签名
		$sha1 = new SHA1;
		$array = $sha1->getSHA1($this->token, $timestamp, $nonce, $encrypt);
		$ret = $array[0];

		if ($ret != 0) {
			return $ret;
		}

		$signature = $array[1];
		if ($signature != $msgSignature) {
			return ErrorCode::$ValidateSignatureError;
		}

		$result = $pc->decrypt($encrypt, $this->appId);
		if ($result[0] != 0) {
			return $result[0];
		}
		$msg = $result[1];

		return ErrorCode::$OK;
	}

}

/**
 * SHA1 class
 *
 * 计算公众平台的消息签名接口.
 */
class SHA1{
	/**
	 * 用SHA1算法生成安全签名
	 * @param string $token 票据
	 * @param string $timestamp 时间戳
	 * @param string $nonce 随机字符串
	 * @param string $encrypt 密文消息
	 */
	public function getSHA1($token, $timestamp, $nonce, $encrypt_msg){
		//排序
		try {
			$array = array($encrypt_msg, $token, $timestamp, $nonce);
			sort($array, SORT_STRING);
			$str = implode($array);
			return array(ErrorCode::$OK, sha1($str));
		} catch (Exception $e) {
			//print $e . "\n";
			return array(ErrorCode::$ComputeSignatureError, null);
		}
	}

}

/**
 * error code 说明.
 * <ul>
 *    <li>-40001: 签名验证错误</li>
 *    <li>-40002: xml解析失败</li>
 *    <li>-40003: sha加密生成签名失败</li>
 *    <li>-40004: encodingAesKey 非法</li>
 *    <li>-40005: appid 校验错误</li>
 *    <li>-40006: aes 加密失败</li>
 *    <li>-40007: aes 解密失败</li>
 *    <li>-40008: 解密后得到的buffer非法</li>
 *    <li>-40009: base64加密失败</li>
 *    <li>-40010: base64解密失败</li>
 *    <li>-40011: 生成xml失败</li>
 * </ul>
 */
class ErrorCode {
	public static $OK = 0;
	public static $ValidateSignatureError = -40001;
	public static $ParseXmlError = -40002;
	public static $ComputeSignatureError = -40003;
	public static $IllegalAesKey = -40004;
	public static $ValidateAppidError = -40005;
	public static $EncryptAESError = -40006;
	public static $DecryptAESError = -40007;
	public static $IllegalBuffer = -40008;
	public static $EncodeBase64Error = -40009;
	public static $DecodeBase64Error = -40010;
	public static $GenReturnXmlError = -40011;
}


